﻿using System;
using System.IO;
using System.Threading;
using System.Windows.Forms;
using Devv.Core.Erroo;
using Devv.Core.Utils;
using Devv.FreshTracker.Entity;

namespace Devv.FreshTracker
{
	public class LocalCache
	{
		#region Properties

		// Paths
		private static String SessionsPath { get { return App.DataPath + "/Sessions.xml"; } }
		private static String TasksPath { get { return App.DataPath + "/Tasks.xml"; } }
		private static String ProjectsPath { get { return App.DataPath + "/Projects.xml"; } }
		private static String ClientsPath { get { return App.DataPath + "/Clients.xml"; } }
		private static String RulesPath { get { return App.DataPath + "/Rules.xml"; } }

		// Collections
		public static ClientCollection Clients { get; set; }
		public static ProjectCollection Projects { get; set; }
		public static TaskCollection Tasks { get; set; }
		public static SessionCollection Sessions { get; set; }
		public static RuleCollection Rules { get; set; }

		// State and cache properties
		public static DateTime LastRefresh { get; set; }
		public static Int32 UploadFailures { get; set; }
		public static Int32 DownloadFailures { get; set; }
		public static Boolean Refreshing { get; set; }

		#endregion

		#region Events

		public delegate void SessionEventHandler(Session s);
		public delegate void EmptyEventHandler();

		public static event SessionEventHandler OnSessionUploadFailed;
		public static event EmptyEventHandler OnFailingToUploadData;
		public static event EmptyEventHandler OnFailingToSyncManyTimes;

		#endregion

		#region General loading and saving

		public static void Load()
		{
			UploadFailures = 0;
			DownloadFailures = 0;
			Refreshing = false;

			LoadClient();
			LoadProject();
			LoadTask();
			LoadSession();
			LoadRule();

			Maintenance.Log("Loaded local cache.");
		}

		public static void Save()
		{
			SaveClient();
			SaveProject();
			SaveTask();
			SaveSession();
			SaveRule();

			Maintenance.Log("Saved local cache.");
		}

		public static void Clear(Boolean clearRules)
		{
			Clients = new ClientCollection();
			Projects = new ProjectCollection();
			Tasks = new TaskCollection();
			Sessions = new SessionCollection();

			if (clearRules)
			{
				Rules = new RuleCollection();
			}

			Maintenance.Log("Cleared local cache, including rules = " +clearRules+ ".");

			Save();
		}

		public static void Refresh()
		{
			if (!Refreshing && DateTime.Now > LastRefresh.AddMinutes(5))
			{
				Refreshing = true;

				try
				{
					Clients = FreshBooks.GetClient();
					Projects = FreshBooks.GetProject();

					TaskCollection mergedTasks = new TaskCollection();

					foreach (Project p in Projects)
					{
						p.Client = Clients.Find(p.Client.Id);

						mergedTasks.AddRange(FreshBooks.GetTasks(p));
					}

					Tasks = mergedTasks;

					Maintenance.Log("Refreshed data from FreshBooks.");
				}
				catch (Exception ex)
				{
					ErrorHandler.Handle(ex);

					DownloadFailures++;

					Maintenance.Log("Error while refreshing data from FreshBooks: " + ex.Message);
				}

				Clients.Sort((s1, s2) => s1.DisplayMember.CompareTo(s2.DisplayMember));
				Projects.Sort((s1, s2) => s1.DisplayMember.CompareTo(s2.DisplayMember));
				Tasks.Sort((s1, s2) => s1.DisplayMember.CompareTo(s2.DisplayMember));
			}

			ValidateSync();

			SaveSession();
			SaveTask();
			SaveProject();
			SaveClient();

			Refreshing = false;
		}

		public static void UploadSavedSessions()
		{
			for (Int32 i = 0; i < Sessions.Count; i++)
			{
				try
				{
					if (FreshBooks.UploadSession(Sessions[i]))
					{
						Sessions.RemoveAt(i);
						i--;
					}
					else
					{
						UploadFailures++;
					}
				}
				catch (Exception ex)
				{
					ErrorHandler.Handle(ex);

					UploadFailures++;

					Maintenance.Log("Error uploading sessions to FreshBooks: " + ex.Message);
				}
			}

			ValidateSync();
		}

		#endregion

		#region Validate sync

		private static void ValidateSync()
		{
			if (UploadFailures + DownloadFailures > 10)
			{
				OnFailingToSyncManyTimes();
			}
			else if (UploadFailures > 5)
			{
				OnFailingToUploadData();
			}
		}

		#endregion

		#region Sessions

		public static void LoadSession()
		{
			if (!File.Exists(SessionsPath))
			{
				Sessions = new SessionCollection();

				return;
			}

			try
			{
				Sessions = SerializationUtil.Deserialize(SessionsPath, typeof(SessionCollection)) as SessionCollection;

				if (Sessions != null)
				{
					Maintenance.Log("Deserialized " + Sessions.Count + " sessions from XML.");
				}
			}
			catch (Exception ex)
			{
				ErrorHandler.Handle(ex);

				try
				{
					File.Delete(SessionsPath);
				}
				catch
				{
					// Oops, probably a file lock
				}

				MessageBox.Show(Messages.ErrorNotLoadSessionCache, Messages.LocalCache, MessageBoxButtons.OK, MessageBoxIcon.Error);
			}

			if (Sessions == null)
			{
				Sessions = new SessionCollection();
			}
		}

		public static void SaveSession()
		{
			try
			{
				String xml = SerializationUtil.Serialize(Sessions);

				IOUtil.WriteFile(SessionsPath, xml, false);

				Maintenance.Log("Serialized " + Sessions.Count + " sessions to XML.");
			}
			catch (Exception ex)
			{
				ErrorHandler.Handle(ex);

				MessageBox.Show(Messages.ErrorNotLoadSessionCache, Messages.LocalCache, MessageBoxButtons.OK, MessageBoxIcon.Error);
			}
		}

		public static void UploadSession(Session s)
		{
			ThreadPool.QueueUserWorkItem(UploadSessionAsync, s);
		}

		private static void UploadSessionAsync(Object session)
		{
			Session s = session as Session;

			if (s == null || !s.HasMinimumTime)
			{
				return;
			}

			if (Options.Round > 0)
			{
				s.Round();
			}

			if (!FreshBooks.UploadSession(s))
			{
				Sessions.Add(s);
				SaveSession();

				UploadFailures++;

				if (UploadFailures < 3)
				{
					OnSessionUploadFailed(s);
				}
				else
				{
					ValidateSync();
				}
			}
		}

		#endregion

		#region Clients

		public static void LoadClient()
		{
			if (!File.Exists(ClientsPath))
			{
				Clients = new ClientCollection();
				Clients.Add(Client.Internal());

				return;
			}

			try
			{
				Clients = SerializationUtil.Deserialize(ClientsPath, typeof(ClientCollection)) as ClientCollection;

				if (Clients != null)
				{
					Maintenance.Log("Deserialized " + Clients.Count + " clients from XML.");
				}
			}
			catch (Exception ex)
			{
				ErrorHandler.Handle(ex);
			}
		}

		public static void SaveClient()
		{
			try
			{
				String xml = SerializationUtil.Serialize(Clients);

				IOUtil.WriteFile(ClientsPath, xml);

				Maintenance.Log("Serialized " + Clients.Count + " sessions to XML.");
			}
			catch (Exception ex)
			{
				ErrorHandler.Handle(ex);
			}
		}

		#endregion

		#region Projects

		public static ProjectCollection GetProjects(Client client)
		{
			if (Projects == null) return null;

			ProjectCollection result = new ProjectCollection();

			foreach (Project p in Projects)
			{
				if (p.Client.Id == client.Id)
				{
					result.Add(p);
				}
			}

			return result;
		}

		public static void LoadProject()
		{
			if (!File.Exists(ProjectsPath))
			{
				Projects = new ProjectCollection();

				return;
			}

			try
			{
				Projects = SerializationUtil.Deserialize(ProjectsPath, typeof(ProjectCollection)) as ProjectCollection;

				if (Projects != null)
				{
					Maintenance.Log("Deserialized " + Projects.Count + " projects from XML.");
				}
			}
			catch (Exception ex)
			{
				ErrorHandler.Handle(ex);
			}

			if (Projects == null)
			{
				Projects = new ProjectCollection();
			}
		}

		public static void SaveProject()
		{
			try
			{
				String xml = SerializationUtil.Serialize(Projects);

				IOUtil.WriteFile(ProjectsPath, xml);

				Maintenance.Log("Serialized " + Projects.Count + " sessions to XML.");
			}
			catch (Exception ex)
			{
				ErrorHandler.Handle(ex);
			}
		}

		#endregion

		#region Tasks

		public static TaskCollection GetTasks(Project project)
		{
			if (Tasks == null) return null;

			TaskCollection result = new TaskCollection();

			foreach (Task t in Tasks)
			{
				if (t.Project.Id == project.Id)
				{
					result.Add(t);
				}
			}

			return result;
		}

		public static void LoadTask()
		{
			if (!File.Exists(TasksPath))
			{
				Tasks = new TaskCollection();

				return;
			}

			try
			{
				Tasks = SerializationUtil.Deserialize(TasksPath, typeof(TaskCollection)) as TaskCollection;

				if (Tasks != null)
				{
					Maintenance.Log("Deserialized " + Tasks.Count + " tasks from XML.");
				}
			}
			catch (Exception ex)
			{
				ErrorHandler.Handle(ex);
			}

			if (Tasks == null)
			{
				Tasks = new TaskCollection();
			}
		}

		public static void SaveTask()
		{
			try
			{
				String xml = SerializationUtil.Serialize(Tasks);

				IOUtil.WriteFile(TasksPath, xml);

				Maintenance.Log("Serialized " + Tasks.Count + " tasks to XML.");
			}
			catch (Exception ex)
			{
				ErrorHandler.Handle(ex);
			}
		}

		#endregion

		#region Rules

		public static void LoadRule()
		{
			if (!File.Exists(RulesPath))
			{
				Rules = new RuleCollection();

				return;
			}

			try
			{
				Rules = SerializationUtil.Deserialize(RulesPath, typeof(RuleCollection)) as RuleCollection;

				if (Rules != null)
				{
					Maintenance.Log("Deserialized " + Rules.Count + " rules from XML.");
				}
			}
			catch (Exception ex)
			{
				ErrorHandler.Handle(ex);
			}

			if (Tasks == null)
			{
				Rules = new RuleCollection();
			}
		}

		public static void SaveRule()
		{
			try
			{
				String xml = SerializationUtil.Serialize(Rules);

				IOUtil.WriteFile(RulesPath, xml);

				Maintenance.Log("Serialized " + Rules.Count + " rules to XML.");
			}
			catch (Exception ex)
			{
				ErrorHandler.Handle(ex);
			}
		}

		#endregion
	}
}